package com.college.common.utils.security;


import com.college.common.utils.log.LogPrintUtils;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

/**
 * AES对称加密：AES常用的有ECB和CBC两种模式。常用的填充方式有PKCS5Padding、PKCS7Padding、zeropadding
 * CBC模式比ECB模式安全，ECB模式比CBC模式快。 推荐使用CBC模式。
 * 相同密码的加密结果不会变化，是固定的。
 * 这个标准用来替代原先的DES
 * <p>
 * key的字节长度只能是16位、24位、32位。
 * iv的字节长度只能是16位。
 * KEY和IV可以相同，也可以不相同。
 * key和iv长度不足时，这里将以0x00自动填充，超出部分将被忽略。注意：其他系统不一定是这种逻辑，所以key建议设置为16/24/32位，iv设置为16位。
 * <p>
 * 说明：AES数据块长度为128位(16字节)，所以IV长度需要为16个字节（ECB模式不用IV），IV与KEY超过长度则截取，不足则在末尾填充'\0'补足
 */
@Slf4j
public class AesEcbCbcUtil {
    private static final String CBCMode = "AES/CBC/PKCS5Padding";//填充方式
    private static final String ECBMode = "AES/ECB/PKCS5Padding";//填充方式

    public static void main(String[] args) {
        String key = "1234567812345678";// key长度建议设置为16位或者24位或者32位。
        String iv = "1234567887654321";// iv长度建议设置为16位。
        String msg = "测试字符串";

        //CBC模式
        String cbcEncrypt = AesEcbCbcUtil.encryptCBC(msg, key, iv);
        System.out.println(cbcEncrypt);
        String cbcDecryptStr = AesEcbCbcUtil.decryptCBC(cbcEncrypt, key, iv);
        System.out.println(cbcDecryptStr);

        //ECB模式
        String ecbEncrypt = AesEcbCbcUtil.encryptECB(msg, key);
        System.out.println(ecbEncrypt);
        String decryptStr = AesEcbCbcUtil.decryptECB(ecbEncrypt, key);
        System.out.println(decryptStr);
    }

    /**
     * AES CBC 加密
     * @param message 需要加密的字符串
     * @param key     密匙
     * @param iv      IV，需要和key长度相同
     * @return 返回加密后密文，编码为base64
     */
    public static String encryptCBC(String message, String key, String iv) {
        try {
            byte[] content = message.getBytes(StandardCharsets.UTF_8);
            byte[] keyByte = key.getBytes(StandardCharsets.UTF_8);
            keyByte = fillKey(keyByte);
            SecretKeySpec keySpec = new SecretKeySpec(keyByte, "AES");
            byte[] ivByte = iv.getBytes(StandardCharsets.UTF_8);
            ivByte = fillIv(ivByte);
            IvParameterSpec ivSpec = new IvParameterSpec(ivByte);
            Cipher cipher = Cipher.getInstance(CBCMode);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
            byte[] data = cipher.doFinal(content);
            return Base64.getEncoder().encodeToString(data);
        } catch (NoSuchAlgorithmException e) {
            LogPrintUtils.info(log,"{}没有这样的算法", e);
        } catch (NoSuchPaddingException e) {
            LogPrintUtils.info(log,"{}没有这样的填充", e);
        } catch (InvalidKeyException e) {
            LogPrintUtils.info(log,"{}无效的密钥", e);
        } catch (InvalidAlgorithmParameterException e) {
            LogPrintUtils.info(log,"{}无效的算法参数", e);
        } catch (IllegalBlockSizeException e) {
            LogPrintUtils.info(log,"{}非法块大小", e);
        } catch (BadPaddingException e) {
            LogPrintUtils.info(log,"{}不良填充", e);
        }
        return null;
    }

    /**
     * AES CBC 解密
     * @param messageBase64 密文，base64编码
     * @param key           密匙，和加密时相同
     * @param iv            IV，需要和key长度相同
     * @return 解密后数据
     */
    public static String decryptCBC(String messageBase64, String key, String iv) {
        try {
            byte[] messageByte = Base64.getDecoder().decode(messageBase64);
            byte[] keyByte = key.getBytes(StandardCharsets.UTF_8);
            keyByte = fillKey(keyByte);
            SecretKeySpec keySpec = new SecretKeySpec(keyByte, "AES");
            byte[] ivByte = iv.getBytes(StandardCharsets.UTF_8);
            ivByte = fillIv(ivByte);
            IvParameterSpec ivSpec = new IvParameterSpec(ivByte);
            Cipher cipher = Cipher.getInstance(CBCMode);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            byte[] content = cipher.doFinal(messageByte);
            return new String(content, StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException e) {
            LogPrintUtils.info(log,"{}没有这样的算法", e);
        } catch (NoSuchPaddingException e) {
            LogPrintUtils.info(log,"{}没有这样的填充", e);
        } catch (InvalidKeyException e) {
            LogPrintUtils.info(log,"{}无效的密钥", e);
        } catch (InvalidAlgorithmParameterException e) {
            LogPrintUtils.info(log,"{}无效的算法参数", e);
        } catch (IllegalBlockSizeException e) {
            LogPrintUtils.info(log,"{}非法块大小", e);
        } catch (BadPaddingException e) {
            LogPrintUtils.info(log,"{}不良填充", e);
        }
        return null;
    }

    /**
     * AES ECB 加密
     * @param message 需要加密的字符串
     * @param key     密匙
     * @return 返回加密后密文，编码为base64
     */
    public static String encryptECB(String message, String key ,Integer keyLen) {
        byte[] keyByte = key.getBytes(StandardCharsets.UTF_8);
        keyByte = fillKey(keyByte,keyLen);
        return encryptECB(message,new String(keyByte));
    }
    /**
     * AES ECB 加密
     * @param message 需要加密的字符串
     * @param key     密匙
     * @return 返回加密后密文，编码为base64
     */
    public static String encryptECB(String message, String key) {
        try {
            byte[] content = message.getBytes(StandardCharsets.UTF_8);
            byte[] keyByte = key.getBytes(StandardCharsets.UTF_8);
            keyByte = fillKey(keyByte);
            SecretKeySpec keySpec = new SecretKeySpec(keyByte, "AES");
            Cipher cipher = Cipher.getInstance(ECBMode);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec);
            byte[] data = cipher.doFinal(content);
            return Base64.getEncoder().encodeToString(data);
        } catch (NoSuchAlgorithmException e) {
            LogPrintUtils.info(log,"{}没有这样的算法", e);
        } catch (NoSuchPaddingException e) {
            LogPrintUtils.info(log,"{}没有这样的填充", e);
        } catch (InvalidKeyException e) {
            LogPrintUtils.info(log,"{}无效的密钥", e);
        } catch (IllegalBlockSizeException e) {
            LogPrintUtils.info(log,"{}非法块大小", e);
        } catch (BadPaddingException e) {
            LogPrintUtils.info(log,"{}不良填充", e);
        }
        return null;
    }

    /**
     * AES ECB 解密
     * @param messageBase64 密文，base64编码
     * @param key           密匙，和加密时相同
     * @return 解密后数据
     */
    public static String decryptECB(String messageBase64, String key) {
        try {
            byte[] messageByte = Base64.getDecoder().decode(messageBase64);
            byte[] keyByte = key.getBytes(StandardCharsets.UTF_8);
            keyByte = fillKey(keyByte);
            SecretKeySpec keySpec = new SecretKeySpec(keyByte, "AES");
            Cipher cipher = Cipher.getInstance(ECBMode);
            cipher.init(Cipher.DECRYPT_MODE, keySpec);
            byte[] content = cipher.doFinal(messageByte);
            return new String(content, StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException e) {
            LogPrintUtils.info(log,"{}没有这样的算法", e);
        } catch (NoSuchPaddingException e) {
            LogPrintUtils.info(log,"{}没有这样的填充", e);
        } catch (InvalidKeyException e) {
            LogPrintUtils.info(log,"{}无效的密钥", e);
        } catch (IllegalBlockSizeException e) {
            LogPrintUtils.info(log,"{}非法块大小", e);
        } catch (BadPaddingException e) {
            LogPrintUtils.info(log,"{}不良填充", e);
        }
        return null;
    }


    /**
     * 填充key
     * key的字节长度只能是16位、24位、32位。
     * key长度不足时，这里将以0x00填充，超出部分将被忽略。
     * @return 填充后的key
     */
    public static byte[] fillKey(byte[] keyByte) {
        int length = keyByte.length;
        int len;
        if (length == 16 || length == 24 || length == 32) {
            return keyByte;
        } else if (length < 16) {
            len = 16;
        } else if (length < 24) {
            len = 24;
        } else {
            len = 32;
        }
        byte[] newKeyByte = new byte[len];
        System.arraycopy(keyByte, 0, newKeyByte, 0, length < len ? length : len);
        return newKeyByte;
    }

    public static byte[] fillKey(byte[] keyByte,Integer len) {
        int length = keyByte.length;
        byte[] newKeyByte = new byte[len];
        System.arraycopy(keyByte, 0, newKeyByte, 0, length < len ? length : len);
        return newKeyByte;
    }

    /**
     * 填充iv
     * iv的字节长度只能是16位。
     * iv长度不足时，这里将以0x00填充，超出部分将被忽略。
     * @return 填充后的iv
     */
    public static byte[] fillIv(byte[] ivByte) {
        int length = ivByte.length;
        int len;
        if (length == 16) {
            return ivByte;
        } else {
            len = 16;
        }
        byte[] newIvByte = new byte[len];
        System.arraycopy(ivByte, 0, newIvByte, 0, length < len ? length : len);
        return newIvByte;
    }
}